package com.easy.mongodb.core.toolkit;

import cn.hutool.json.JSONUtil;
import com.easy.mongodb.common.constants.BaseMongoConstants;
import com.easy.mongodb.common.utils.LogUtils;
import com.easy.mongodb.core.biz.TableInfo;
import com.easy.mongodb.core.cache.GlobalConfigCache;
import com.easy.mongodb.core.config.GlobalConfig;
import com.mongodb.client.MongoDatabase;
import com.mongodb.client.model.IndexModel;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.bson.BsonDocument;
import org.bson.BsonValue;
import org.bson.Document;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;


/**
 * 索引工具类
 * <p>
 * Copyright © 2022 xpc1024 All Rights Reserved
 **/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class IndexUtils {
    /**
     * 查找所有的索引
     *
     * @param client    MongoClient
     * @param tableInfo 索引名
     * @return 是否存在
     */
    public static Map<String, Document> getIndexs(MongoDatabase client, TableInfo tableInfo) {
        Map<String, Document> indexes = new HashMap<>();
        client.getCollection(tableInfo.getCollectionName()).listIndexes().forEach(e -> {
            if (e.containsKey("name")) {
                indexes.put(e.get("name").toString(), (Document) e.get("key"));
            }
        });
        return indexes;
    }


    /**
     * 判断索引是否需要变更
     *
     * @param indexInfo  mongo中的索引信息
     * @param tableIndex 配置中的索引信息
     * @return 是否需要更新索引
     */
    public static boolean isIndexNeedChange(Document indexInfo, IndexModel tableIndex) {
        String existsIndex = createIndexName(indexInfo.toBsonDocument());
        String entityIndex = createIndexName(tableIndex.getKeys().toBsonDocument());
        // 与查询到的已知index对比是否发生改变
        return !existsIndex.equalsIgnoreCase(entityIndex);
    }


    /**
     * 异步执行索引托管操作
     *
     * @param biFunction  索引变更方法
     * @param entityClass 实体类
     * @param client      MongoClient
     */
    public static void supplyAsync(BiFunction<Class<?>, MongoDatabase, Boolean> biFunction, Class<?> entityClass, MongoDatabase client) {
        CompletableFuture<Boolean> completableFuture = CompletableFuture.supplyAsync(() -> {
            GlobalConfig globalConfig = GlobalConfigCache.getGlobalConfig();
            if (!globalConfig.isDistributed()) {
                // 非分布式项目, 直接处理
                return biFunction.apply(entityClass, client);
            }
            try {
                // 尝试获取分布式锁
                boolean lock = LockUtils.tryLock(client, entityClass.getSimpleName().toLowerCase(), BaseMongoConstants.LOCK_MAX_RETRY);
                if (!lock) {
                    LogUtils.warn(entityClass.getSimpleName().toLowerCase(), "retry get distribute lock failed, please check whether other resources have been preempted or deadlocked");
                    return Boolean.FALSE;
                }
                return biFunction.apply(entityClass, client);
            } finally {
                LockUtils.release(client, entityClass.getSimpleName().toLowerCase(), BaseMongoConstants.LOCK_MAX_RETRY);
            }
        }).exceptionally((throwable) -> {
            Optional.ofNullable(throwable).ifPresent(e -> LogUtils.error(entityClass.getSimpleName().toLowerCase(), "process index exception", e.toString()));
            return Boolean.FALSE;
        }).whenCompleteAsync((success, throwable) -> {
            if (success) {
                LogUtils.info(entityClass.getSimpleName().toLowerCase(), "===> Congratulations auto process index by Easy-MongoDB is done !");
            } else {
                LogUtils.warn(entityClass.getSimpleName().toLowerCase(), "===> Unfortunately, auto process index by Easy-MongoDB failed, please check your configuration");
            }
        });

        // 是否开启阻塞 默认开启 运行测试模块时建议开启阻塞,否则测试用例跑完后,主线程退出,但异步线程可能还没跑完,可能出现死锁
        // 生产环境或迁移数据量比较大的情况下,可以配置开启非阻塞,这样服务启动更快
        GlobalConfig globalConfig = GlobalConfigCache.getGlobalConfig();
        if (globalConfig.isAsyncProcessIndexBlocking()) {
            completableFuture.join();
        }
    }

    public static String createIndexName(BsonDocument document) {
        SortedMap<String, BsonValue> resultMap = new TreeMap<String, BsonValue>(JSONUtil.toBean(document.toJson(), Map.class));
        StringBuffer sb = new StringBuffer();
        Set es = resultMap.entrySet();//所有参与传参的参数按照accsii排序（升序）
        Iterator it = es.iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            String k = (String) entry.getKey();
            Object v = entry.getValue();
            if (null != v && !"".equals(v)) {
                sb.append(k + "_" + v + "_");
            }
        }
        return sb.toString();
    }
}
